﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Isuka.Algorithms;
using Isuka.Excel;
using System.Globalization;

namespace isuka.samples.excel
{
    public static class Domain
    {
        static readonly string[] ExcelExtensions = new[] { ".XLS", ".XLSX", ".CSV" };

        /// <summary>
        /// ファイルがExcelかどうか調べる
        /// </summary>
        /// <param name="filepath"></param>
        /// <returns></returns>
        public static bool IsExcelFile(string filepath)
        {
            if (File.Exists(filepath))
            {
                var ext = Path.GetExtension(filepath).ToUpper();
                return ExcelExtensions.Contains(ext);
            }
            return false;
        }


        static internal StrategySummary[] Analysis(string filename)
        {
            using (var app = new XApplication())
            {
                var begin = app.Workbooks.Open(filename).Worksheets[0].Cells[1, 0];
                return
                    begin.ToEnd(XDirection.Down).ToModels(row =>
                    {
                        return new StrategySummary
                        {
                            Name = row.A,
                            Pair = row.B,
                            NumOfDeals = (int)row.D,
                            MaxDrowdown = row.E,
                            TotalProfit = row.F,
                            WinPercentage = row.H / 100,
                            ProfitFactor = row.K,
                            AvgProfit = row.M,
                            AvgLoss = row.O,
                            StartDate = DateTime.ParseExact(row.L, "MM/dd/yyyy", DateTimeFormatInfo.InvariantInfo),
                        };
                    })
                    .AsParallel()
                    .Select(x =>
                    {
                        x.ExpectedValue = ComputeExpectedValue(x.WinPercentage, x.AvgProfit, x.AvgLoss);
                        x.RuinProbability = ComputeRuinProbability(x.WinPercentage, x.AvgProfit, x.AvgLoss, 0.1);
                        x.BetterProfitFactor = IsBetterProfitFactor(x.NumOfDeals, x.ProfitFactor);
                        return x;
                    })
                    .Where(x => x.StartDate <= DateTime.Today.AddMonths(-6))
                    .Where(x => x.ExpectedValue > 0 && x.RuinProbability < 0.05)  //  期待値がプラスかつ、破産確率が10%以下
                    .OrderByDescending(x => x.ExpectedValue)
                    .ThenBy(x => x.RuinProbability)
                    .ThenByDescending(x => x.ProfitFactor)
                    .ToArray()
                    ;
            }
        }

        /// <summary>
        /// 期待値の計算
        /// </summary>
        /// <param name="P">勝率</param>
        /// <param name="avgProfit">平均利益</param>
        /// <param name="avgLoss">平均損失</param>
        /// <returns></returns>
        static double ComputeExpectedValue(double P, double avgProfit, double avgLoss)
        {
            return (P * avgProfit) + ((1 - P) * avgLoss);
        }

        /// <summary>
        /// 破産確率の計算
        /// </summary>
        /// <param name="P">勝率</param>
        /// <param name="avgProfit">平均利益</param>
        /// <param name="avgLoss">平均損失</param>
        /// <param name="risk">リスク率</param>
        /// <returns></returns>
        static double ComputeRuinProbability(double P, double avgProfit, double avgLoss, double risk)
        {
            avgLoss = Math.Abs(avgLoss);
            if (P <= (avgLoss / (avgProfit + avgLoss)))
            {
                //  期待値が0以下なので、破産確率100%
                return 1;
            }

            var R = avgProfit / avgLoss;

            //  以下の式 x を解く
            //  f(x) =  P * x ** (R+1) - x + 1 - P
            Func<double, double> f = x => P * (Math.Pow(x, R + 1)) - x + 1 - P;
            var S = Calculation.Bisection(f, 0, 1);
            return Math.Round(Math.Min(Math.Pow(S, 1 / risk), 1) * 1000) / 1000;
        }

        /// <summary>
        /// PFが1の場合の標準偏差の上限を超えるPFかどうか
        /// </summary>
        /// <param name="numOfDeals">取引回数</param>
        /// <param name="profitFactor">プロフィットファクター</param>
        /// <returns></returns>
        static bool IsBetterProfitFactor(int numOfDeals, double profitFactor)
        {
            var d = Math.Sqrt(numOfDeals) * 1.96;
            var stdpf = (numOfDeals + d) / (numOfDeals - d);
            return stdpf < profitFactor;
        }
    }
}
